import logging
import math
import datetime
from itertools import groupby
from collections import Counter

from odoo import _, api, fields, models, Command, SUPERUSER_ID
from odoo.exceptions import AccessError, ValidationError, UserError
from odoo.tests import Form
from odoo.tools import html2plaintext, float_compare, float_is_zero
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES

_logger = logging.getLogger(__name__)


class Rma(models.Model):
    _name = 'rma'
    _description = 'RMA'
    _order = 'date desc'
    _inherit = ['mail.thread', 'portal.mixin', 'mail.activity.mixin']

    name = fields.Char(index=True, readonly=True, copy=False, default=lambda self: _('New'))
    origin = fields.Char(string='Source Document', help='Reference of the document that generated this RMA.')
    date = fields.Datetime(default=fields.Datetime.now, index=True, required=True)
    deadline = fields.Date('Deadline')
    user_id = fields.Many2one('res.users', string='Responsible', index=True, tracking=True, default=lambda self: self.env.user.id)
    team_id = fields.Many2one('rma.team',string='RMA team', index=True, compute='_compute_team_id', store=True)
    tag_ids = fields.Many2many(comodel_name='rma.tag', string='Tags')
    finalization_id = fields.Many2one(
        string='Finalization Reason',
        comodel_name='rma.finalization',
        copy=False,
        readonly=True,
        domain=(
            "['|', ('company_id', '=', False), ('company_id', '='," " company_id)]"
        ),
        tracking=True,
    )
    company_id = fields.Many2one(comodel_name='res.company', default=lambda self: self.env.company)
    partner_id = fields.Many2one('res.partner', 'Customer', index=True, tracking=True, required=True)
    partner_shipping_id = fields.Many2one(
        string='Shipping Address',
        comodel_name='res.partner',
        help='Shipping address for current RMA.',
        compute='_compute_partner_shipping_id',
        store=True,
        readonly=False,
    )
    partner_invoice_id = fields.Many2one(
        string='Invoice Address',
        comodel_name='res.partner',
        domain=(
            "['|', ('company_id', '=', False), ('company_id', '='," " company_id)]"
        ),
        help='Refund address for current RMA.',
        compute='_compute_partner_invoice_id',
        store=True,
        readonly=False,
    )
    commercial_partner_id = fields.Many2one('res.partner', related='partner_id.commercial_partner_id')

    product_id = fields.Many2one('product.product', domain=[('type', 'in', ['consu', 'product'])])
    product_uom_qty = fields.Integer(string='Quantity', required=True, default=1)
    product_uom = fields.Many2one('uom.uom', string='UoM', required=True, related='product_id.uom_id')

    operation = fields.Selection([
        ('refund', 'Refund'),
        ('return_repair', 'Return Repair'),
        ('no_return_repair', 'Not Return Repair')
    ], 'Operation', required=True)

    state = fields.Selection([
        ('draft', 'Draft'),
        ('sale_confirmed', 'Sale Confirmed'),
        ('confirmed', 'Confirmed'),
        ('received', 'Received'),
        ('waiting_return', 'Waiting for return'),
        ('waiting_replacement', 'Waiting for replacement'),
        ('refunded', 'Refunded'),
        ('returned', 'Returned'),
        ('replaced', 'Replaced'),
        ('reworking', 'Reworking'),
        ('rework', 'Rework'),
        ('remaking', 'Remaking'),
        ('remake', 'Remake'),
        ('finished', 'Finished'),
        ('locked', 'Locked'),
        ('cancelled', 'Canceled'),
    ], default='draft', copy=False, tracking=True, compute='_compute_state', store=True)

    description = fields.Html('Description')

    refund_line_ids = fields.One2many('account.move.line', 's_rma_id', readonly=True, copy=False)
    s_refund_count = fields.Integer('refund_count', compute='_compute_refund_count', store=True)

    # 待退款数量
    qty_refunded = fields.Float('refunded qty', compute='_compute_qty_refunded', store=True)

    can_be_finished = fields.Boolean( compute='_compute_can_be_finished', )
    remaining_qty = fields.Float(
        string='Remaining delivered qty',
        digits='Product Unit of Measure',
        compute='_compute_remaining_qty',
    )
    remaining_qty_to_done = fields.Float(
        string='Remaining delivered qty to done',
        digits='Product Unit of Measure',
        compute='_compute_remaining_qty',
    )
    uom_category_id = fields.Many2one(
        related='product_id.uom_id.category_id', string='Category UoM'
    )

    origin_split_rma_id = fields.Many2one(
        comodel_name='rma',
        string='Extracted from',
        readonly=True,
        copy=False,
    )

    rm_production_ids = fields.One2many('mrp.production', 's_rma_id', string='Manufacturing Order(R/M)', readonly=True, copy=False)
    rm_qty = fields.Float(digits='Product Unit of Measure', compute='_compute_scrap_qty', store=True)
    rm_production_count = fields.Integer(
        string='Manufacturing Order (R/M) Counting', compute='_compute_scrap_qty', store=True
    )

    s_rma_picking_ids = fields.One2many('stock.picking', 's_rma_id', readonly=True)
    s_rma_picking_count = fields.Integer('picking count', compute='_compute_rma_picking_count', store=True)
    s_rma_move_ids = fields.One2many('stock.move', 's_rma_id', readonly=True)

    reception_qty = fields.Integer(
        string='Reception Qty', digits='Product Unit of Measure', compute='_compute_reception_qty', store=True
    )

    remake_qty = fields.Integer('Remake Qty', digits='Product Unit of Measure' )
    scrap_qty = fields.Integer(
        string='Scrap Qty', digits='Product Unit of Measure',  compute='_compute_scrap_qty', store=True, readonly=False
    )
    good_qty = fields.Integer(
        string='Good Qty for RMA', digits='Product Unit of Measure', compute='_compute_scrap_qty', store=True
    )

    s_order_ids = fields.Many2many('sale.order', 's_rma_sale_order_rel', string='MO')
    s_order_id = fields.Many2one('sale.order', string='MO')
    # s_picking_ids = fields.Many2many('stock.picking', string='Domain pickings', compute='_compute_s_picking_ids', store=True)
    attachment_ids = fields.Many2many('ir.attachment',string='Attachments')
    s_to_refund = fields.Boolean(string='Update Mo delivery qty', default=True, help='为真：销售单的已发货数量退回，可再次做出货指示')
    s_reason = fields.Char(string='Reasons for Return')
    s_invoice_ids = fields.Many2many('account.move', string='Invoice')

    s_refunded = fields.Boolean('Refunded', default=False, copy=False, readonly=True)

    s_return_line_ids = fields.One2many('s.rma.return.line', 'rma_id')

    sale_line_id = fields.Many2one('sale.order.line')

    s_quality_description = fields.Html('Quality Description', copy=False)

    @api.onchange('product_id')
    def onchange_product_id(self):
        if not self.product_id:
            return

        # 多版本产品
        product_ids = self.product_id.s_base_id.product_tmpl_ids.mapped('product_variant_ids')
        product_ids |= self.product_id

        sale_line_ids = self.env['sale.order.line'].search([
            ('product_id', 'in', product_ids.ids),
            ('qty_delivered', '>', 0),
            ('state', 'in', ('sale', 'done'))
        ])
        self.s_return_line_ids = [(5, 0)]
        vals_list = []
        for line in sale_line_ids:
            vals_list.append((0, 0, {
                'sale_id': line.order_id.id,
                'sale_line_id': line.id,
                'product_id': line.product_id.id,
                'order_qty': line.product_uom_qty,
                'delivery_qty': line.qty_delivered,
                'product_uom_id': line.product_uom.id,
                'order_date': line.order_id.create_date
            }))
        self.s_return_line_ids = vals_list

    @api.depends('refund_line_ids.move_id.state', 'refund_line_ids.quantity')
    def _compute_qty_refunded(self):
        for rec in self:
            refund_line_ids = rec.refund_line_ids.filtered(lambda x: x.move_id.state != 'cancel')
            rec.qty_refunded = rec.product_uom_qty - sum(refund_line_ids.mapped('quantity'))

    @api.depends('s_rma_picking_ids')
    def _compute_rma_picking_count(self):
        for rec in self:
            rec.s_rma_picking_count = len(rec.s_rma_picking_ids)

    @api.depends('refund_line_ids')
    def _compute_refund_count(self):
        for rec in self:
            rec.s_refund_count = len(rec.refund_line_ids.mapped('move_id'))

    @api.depends('rm_production_ids.state', 'rm_production_ids.qty_reject')
    def _compute_scrap_qty(self):
        for rec in self:
            production_ids = rec.rm_production_ids.filtered(lambda x: x.state != 'cancel' and x.mo_type == 'W')
            done_production_ids = rec.rm_production_ids.filtered(lambda x: x.state == 'done' and x.mo_type == 'W')
            rm_qty = sum(production_ids.mapped('product_qty'))
            rm_production_count = len(production_ids)
            scrap_qty = sum(done_production_ids.mapped('qty_reject'))
            good_qty = sum(done_production_ids.mapped('product_qty')) - scrap_qty
            rec.write({
                'rm_qty': rm_qty,
                'rm_production_count': rm_production_count,
                'scrap_qty': scrap_qty,
                'good_qty': good_qty
            })

    @api.depends('rm_production_ids', 'rm_production_ids.state')
    def _compute_state(self):
        for rec in self:
            if rec.rm_production_ids.filtered(lambda x: x.state not in ['done', 'cancel'] and x.mo_type == 'R'):
                rec.state = 'reworking'
            if rec.rm_production_ids.filtered(lambda x: x.state == 'done' and x.mo_type == 'R'):
                rec.state = 'rework'
            if rec.rm_production_ids.filtered(lambda x: x.state not in ['done', 'cancel'] and x.mo_type == 'M'):
                rec.state = 'remaking'
            if rec.rm_production_ids.filtered(lambda x: x.state == 'done' and x.mo_type == 'M'):
                rec.state = 'remake'

    @api.depends('s_rma_move_ids.state')
    def _compute_reception_qty(self):
        for rec in self:
            s_rma_move_ids = rec.s_rma_move_ids.filtered(lambda x: x.state == 'done')
            if s_rma_move_ids:
                rec.reception_qty = sum(s_rma_move_ids.mapped('quantity'))
            else:
                rec.reception_qty = 0

    def _compute_delivery_picking_count(self):
        # It is enough to count the moves to know how many pickings
        # there are because there will be a unique move linked to the
        # same picking and the same rma.
        rma_data = self.env["stock.move"].read_group(
            [("rma_id", "in", self.ids)],
            ["rma_id", "picking_id"],
            ["rma_id", "picking_id"],
            lazy=False,
        )
        mapped_data = Counter(map(lambda r: r["rma_id"][0], rma_data))
        for record in self:
            record.delivery_picking_count = mapped_data.get(record.id, 0)

    @api.depends("product_uom_qty", 'rm_production_ids.state', 'rm_production_ids.product_qty')
    def _compute_remaining_qty(self):
        """
        计算待检修数量, 已生成检修数量
        """
        for rec in self:
            remaining_qty_to_done = sum(
                rec.rm_production_ids.filtered(lambda x: x.mo_type == 'W' and x.state != 'cancel').mapped('product_qty')
            )
            rec.write({
                'remaining_qty_to_done': remaining_qty_to_done,
                'remaining_qty': rec.product_uom_qty - remaining_qty_to_done
            })

    @api.depends("state", "remaining_qty")
    def _compute_can_be_finished(self):
        for rma in self:
            rma.can_be_finished = (
                    rma.state in {"received", "waiting_replacement", "waiting_return"}
                    and rma.remaining_qty == 0  # org = rma.remaining_qty > 0
            )

    @api.depends("user_id")
    def _compute_team_id(self):
        self.team_id = False
        for record in self.filtered("user_id"):
            record.team_id = (
                self.env["rma.team"]
                .sudo()
                .search(
                    [
                        "|",
                        ("user_id", "=", record.user_id.id),
                        ("member_ids", "=", record.user_id.id),
                        "|",
                        ("company_id", "=", False),
                        ("company_id", "child_of", record.company_id.ids),
                    ],
                    limit=1,
                )
            )

    @api.depends("partner_id")
    def _compute_partner_shipping_id(self):
        self.partner_shipping_id = False
        for record in self.filtered("partner_id"):
            address = record.partner_id.address_get(["delivery"])
            record.partner_shipping_id = address.get("delivery", False)

    @api.depends("partner_id")
    def _compute_partner_invoice_id(self):
        self.partner_invoice_id = False
        for record in self.filtered("partner_id"):
            address = record.partner_id.address_get(["invoice"])
            record.partner_invoice_id = address.get("invoice", False)

    def _compute_access_url(self):
        for record in self:
            record.access_url = "/my/rmas/{}".format(record.id)

    # Constrains methods (@api.constrains)
    @api.constrains(
        "state",
        "partner_id",
        "partner_shipping_id",
        "partner_invoice_id",
        "product_id",
    )
    def _check_required_after_draft(self):
        """Check that RMAs are being created or edited with the
        necessary fields filled out. Only applies to 'Draft' and
        'Cancelled' states.
        """
        rma = self.filtered(lambda r: r.state not in ["draft", "cancelled"])

    # CRUD methods (ORM overrides)
    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if vals.get("name", _("New")) == _("New"):
                ir_sequence = self.env["ir.sequence"]
                if "company_id" in vals:
                    ir_sequence = ir_sequence.with_company(vals["company_id"])
                vals["name"] = ir_sequence.next_by_code("rma")
            # Assign a default team_id which will be the first in the sequence
            if not vals.get("team_id"):
                vals["team_id"] = self.env["rma.team"].search([], limit=1).id
        rmas = super().create(vals_list)
        # Send acknowledge when the RMA is created from the portal and the
        # company has the proper setting active. This context is set by the
        # `rma_sale` module.
        if self.env.context.get("from_portal"):
            rmas._send_draft_email()
        return rmas

    def copy(self, default=None):
        team = super().copy(default)
        for follower in self.message_follower_ids:
            team.message_subscribe(
                partner_ids=follower.partner_id.ids,
                subtype_ids=follower.subtype_ids.ids,
            )
        return team

    def unlink(self):
        if self.filtered(lambda r: r.state != "draft"):
            raise ValidationError(_("You cannot delete RMAs that are not in draft state"))
        return super().unlink()

    def _send_draft_email(self):
        """Send customer notifications they place the RMA from the portal"""
        for rma in self.filtered("company_id.send_rma_draft_confirmation"):
            rma_template_id = rma.company_id.rma_mail_draft_confirmation_template_id.id
            rma.with_context(
                force_send=True,
                mark_rma_as_sent=True,
                default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
            ).message_post_with_template(rma_template_id)

    def _send_confirmation_email(self):
        """Auto send notifications"""
        for rma in self.filtered(lambda p: p.company_id.send_rma_confirmation):
            rma_template_id = rma.company_id.rma_mail_confirmation_template_id.id
            rma.with_context(
                force_send=True,
                mark_rma_as_sent=True,
                default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
            ).message_post_with_template(rma_template_id)

    def _send_receipt_confirmation_email(self):
        """Send customer notifications when the products are received"""
        for rma in self.filtered("company_id.send_rma_receipt_confirmation"):
            rma_template_id = (
                rma.company_id.rma_mail_receipt_confirmation_template_id.id
            )
            rma.with_context(
                force_send=True,
                mark_rma_as_sent=True,
                default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
            ).message_post_with_template(rma_template_id)

    # Action methods
    def action_rma_send(self):
        self.ensure_one()
        template = self.env.ref("rma.mail_template_rma_notification", False)
        template = self.company_id.rma_mail_confirmation_template_id or template
        form = self.env.ref("mail.email_compose_message_wizard_form", False)
        # default_res_ids
        ctx = {
            "default_model": "rma",
            "default_subtype_id": self.env.ref("rma.mt_rma_notification").id,
            # "default_res_id": self.ids[0],
            "default_res_ids": self.ids,
            "default_use_template": bool(template),
            "default_template_id": template and template.id or False,
            "default_composition_mode": "comment",
            "mark_rma_as_sent": True,
            "model_description": "RMA",
            "force_email": True,
        }
        return {
            "type": "ir.actions.act_window",
            "view_type": "form",
            "view_mode": "form",
            "res_model": "mail.compose.message",
            "views": [(form.id, "form")],
            "view_id": form.id,
            "target": "new",
            "context": ctx,
        }

    def _add_message_subscribe_partner(self):
        if self.partner_id and self.partner_id not in self.message_partner_ids:
            self.message_subscribe([self.partner_id.id])

    def action_sale_confirm(self):
        """ 市场确认 """
        self.ensure_one()
        if self.state == 'draft':
            self.state = 'sale_confirmed'

    def action_return_sale(self):
        """ 退回市场 """
        self.ensure_one()
        if self.state == 'sale_confirmed':
            self.state = 'draft'

    def _create_return_picking(self):
        picking_type_id = self.env['stock.picking.type'].search([('barcode', '=', 'WTT-SALE-RS')], limit=1)
        move_vals_list = []

        picking_vals = {
            'partner_id': self.partner_id.id,
            'company_id': self.env.user.company_id.id,
            'location_id': picking_type_id.default_location_src_id.id,
            'location_dest_id': picking_type_id.default_location_dest_id.id,
            'picking_type_id': picking_type_id.id,
            'origin': self.name,
            's_rma_id': self.id
        }
        picking = self.env['stock.picking'].with_user(SUPERUSER_ID).create(picking_vals)
        for line in self.s_return_line_ids.filtered(lambda x: x.return_qty > 0):
            line_move_ids = line.sale_line_id.move_ids.filtered(lambda x: x.location_dest_id.usage == 'customer' and x.state == 'done').sorted(lambda x: x.product_id.id)
            need_qty = line.return_qty
            for product_id, g in groupby(line_move_ids, key=lambda x: x.product_id):
                g_move_ids = self.env['stock.move'].concat(*g)
                for move in g_move_ids:
                    if float_is_zero(need_qty, precision_rounding=1):
                        break
                    qty = min([need_qty, move.quantity])
                    vals = {
                        'name': self.product_id.name,
                        'product_id': self.product_id.id,
                        'location_id': picking_type_id.default_location_src_id.id,
                        'location_dest_id': picking_type_id.default_location_dest_id.id,
                        'product_uom_qty': qty,
                        'picking_id': picking.id,
                        'sale_line_id': line.sale_line_id.id,
                        'to_refund': True,
                        's_rma_id': self.id
                    }
                    move_line_vals_list = []
                    move_need_qty = qty
                    for move_line in move.move_line_ids:
                        if float_is_zero(move_need_qty, precision_rounding=1):
                            break
                        move_lin_qty = min([move_line.quantity, move_need_qty])
                        move_line_vals_list.append((0, 0, {
                            'product_id': product_id.id,
                            'product_uom_id': self.product_uom.id,
                            'location_id': picking_type_id.default_location_src_id.id,
                            'location_dest_id': picking_type_id.default_location_dest_id.id,
                            'picking_id': picking.id,
                            'company_id': self.env.user.company_id.id,
                            'quantity': move_lin_qty,
                            'lot_id': move_line.lot_id.id
                        }))
                        move_need_qty -= move_lin_qty

                    vals['move_line_ids'] = move_line_vals_list
                    move_vals_list.append(vals)

        self.env['stock.move'].with_user(SUPERUSER_ID).create(move_vals_list)
        picking.action_confirm()
        return picking

    def _automatically_match_returns(self):
        """
        自动匹配退货， 先从半年前开始到现在的交货中匹配，不够再往前匹配数量
        """
        self.ensure_one()
        sale_line_ids = self.s_return_line_ids.mapped('sale_line_id')
        out_move_ids = sale_line_ids.mapped('move_ids').filtered(lambda x: x.location_dest_id.usage == 'customer' and x.state == 'done')

        # 半年前
        half_year_ago = datetime.datetime.now() - datetime.timedelta(days=180)
        half_out_move_ids = out_move_ids.filtered(lambda x: x.date >= half_year_ago)
        need_qty = self.product_uom_qty - sum(self.s_return_line_ids.mapped('return_qty'))
        for line in half_out_move_ids.mapped('sale_line_id'):
            if float_is_zero(need_qty, precision_rounding=1):
                break
            self_line = self.s_return_line_ids.filtered(lambda x: x.sale_line_id == line)
            if not self_line:
                continue
            available_quantity = sum(self_line.mapped('delivery_qty')) - sum(self_line.mapped('return_qty'))
            qty = min([available_quantity, need_qty])
            need_qty -= qty
            self_line[0].return_qty = self_line[0].return_qty + qty

        if not float_is_zero(need_qty, precision_rounding=1):
            other_out_move_ids = out_move_ids - half_out_move_ids
            for other_line in other_out_move_ids.mapped('sale_line_id'):
                if float_is_zero(need_qty, precision_rounding=1):
                    break
                other_self_line = self.s_return_line_ids.filtered(lambda x: x.sale_line_id == other_line.id)
                if not other_self_line:
                    continue
                available_quantity = sum(other_self_line.mapped('delivery_qty')) - sum(other_self_line.mapped('return_qty'))
                qty = min([available_quantity, need_qty])
                need_qty -= qty
                other_self_line[0].return_qty = other_self_line[0].return_qty + qty

        if not float_is_zero(need_qty, precision_rounding=1):
            raise ValidationError(_('RMA quantity is greater than the total quantity of sales orders'))

    def action_confirm(self):
        """Invoked when 'Confirm' button in rma form view is clicked."""
        self.ensure_one()

        if not self.s_quality_description:
            raise ValidationError(_('Please fill in the quality confirmation description'))

        if self.state == 'sale_confirmed':
            if float_is_zero(self.product_uom_qty, precision_rounding=1):
                raise ValidationError(_('The quantity must be greater than 0'))
            if self.operation in ('return_repair', 'no_return_repair'):
                # 销售订单总交货数量
                delivery_total = sum(self.s_return_line_ids.mapped('delivery_qty'))
                out_return_total = sum(self.s_return_line_ids.mapped('return_qty'))
                rounding = self.product_uom.rounding
                if float_compare(self.product_uom_qty, delivery_total, precision_rounding=rounding) == 1:
                    raise ValidationError(_('RMA quantity is greater than the total quantity of sales orders'))
                compare = float_compare(self.product_uom_qty, out_return_total,precision_rounding=rounding)

                if compare == -1:
                    raise ValidationError(_('The total number of returns in the detailed line cannot exceed the RMA quantity!'))
                elif compare == 1:
                    self._automatically_match_returns()

            if self.operation == 'return_repair':
                rma_picking = self._create_return_picking()
                    # reception_move, rma_picking = self._create_receptions_from_product()
                self.write({
                    # "reception_move_id": reception_move.id,
                    "state": "confirmed",
                    # 'scrap_qty': self.product_uom_qty
                })
                # self._add_message_subscribe_partner()
                # self._send_confirmation_email()
                # if self.s_to_refund:
                #     self.move_id.sale_line_id.order_id.indicate_state = 'part_confirmed'
            elif self.operation == 'no_return_repair':
                rma_picking = self._create_return_picking()
                rma_picking.button_validate()
                self._automatic_scrapping_return(rma_picking)
                self.write({'scrap_qty': self.product_uom_qty})

            self.state = 'confirmed'

    def _automatic_scrapping_return(self, return_picking):
        if not return_picking:
            return
        location_id = self.env['stock.location'].sudo().search([('barcode', '=', 'WH-RMA')], limit=1)
        reason = self.env['scrap.reason'].search([('name', '=', '其它')], limit=1)
        vals_list = []
        for line in return_picking.move_ids.mapped('move_line_ids').filtered(lambda x: x.state == 'done'):
            vals_list.append({
                'scrap_type': 'base',
                'product_id': line.product_id.id,
                'product_uom_id': line.product_uom_id.id,
                'location_id': location_id.id,
                # 'production_id': self.id,
                'lot_id': line.lot_id.id,
                'scrap_qty': line.quantity,
                'origin': self.name,
                'scrap_line_ids': [(0, 0, {
                    'scrapreason_id': reason.id,
                    'qty_reject': line.quantity,
                })]
            })
        if vals_list:
            scrap_ids = self.env['stock.scrap'].with_user(SUPERUSER_ID).create(vals_list)
            for scrap in scrap_ids:
                scrap.action_validate()

    def action_refund(self):
        """
        退款
        """
        for rec in self:
            origin = rec.name

            if rec.operation in ('return_repair','no_return_repair'):
                return_line_ids = self.s_return_line_ids.filtered(lambda x: x.return_qty > 0).sorted(lambda x: (x.sale_id.currency_id.id, x.product_id.id))
                for (currency_id, product_id), g in groupby(return_line_ids, key=lambda x: (x.sale_id.currency_id, x.product_id)):
                    line_ids = self.env['s.rma.return.line'].concat(*g)
                    vals = {
                        "move_type": "out_refund",
                        "company_id": self.company_id.id,
                        "partner_id": self.partner_invoice_id.id,
                        "invoice_payment_term_id": False,
                        "invoice_origin": self.name,
                        'currency_id': currency_id.id,
                        "invoice_line_ids": [],
                    }
                    refund = self.env["account.move"].sudo().create(vals)
                    invoice_line_vals_list = []
                    for line in line_ids:
                        invoice_line_vals_list.append((0, 0, {
                            's_rma_id': self.id,
                            'currency_id': currency_id.id,
                            'display_type': 'product',
                            'sale_line_ids': [(4, line.sale_line_id.id)],
                            'product_id': product_id.id,
                            'quantity': line.return_qty,
                            'product_uom_id': line.product_uom_id.id,
                            'price_unit': line.sale_line_id.price_unit,
                            'tax_ids': [(4, tax.id) for tax in line.sale_line_id.tax_id]
                        }))

                    refund.invoice_line_ids = invoice_line_vals_list

            else:
                refund_vals = rec._prepare_refund_vals(origin)
                refund_vals["invoice_line_ids"].append((0, 0, rec._prepare_refund_line_vals()))
                refund = self.env["account.move"].sudo().create(refund_vals)
                for line in refund.invoice_line_ids:
                    line.s_rma_id.write({
                        # 'refund_line_id': line.id,
                        'state': 'refunded',
                        # 's_refunded': True
                    })

    def action_replace(self):
        """Invoked when 'Replace' button in rma form view is clicked."""
        self.ensure_one()
        self._ensure_can_be_replaced()
        # Force active_id to avoid issues when coming from smart buttons
        # in other models
        action = (
            self.env.ref('rma.rma_delivery_wizard_action')
            .sudo()
            .with_context(active_id=self.id)
            .read()[0]
        )
        action['name'] = 'Exchange Goods'
        action['context'] = dict(self.env.context)
        action['context'].update(
            active_id=self.id,
            active_ids=self.ids,
            rma_delivery_type='replace',
        )
        return action

    def action_finish(self):
        """Invoked when a user wants to manually finalize the RMA"""
        self.ensure_one()
        self._ensure_can_be_returned()
        # Force active_id to avoid issues when coming from smart buttons
        # in other models
        action = (
            self.env.ref('rma.rma_finalization_wizard_action')
            .sudo()
            .with_context(active_id=self.id)
            .read()[0]
        )
        action['context'] = dict(self.env.context)
        action['context'].update(active_id=self.id, active_ids=self.ids)
        return action

    def action_cancel(self):
        """取消."""

        self.write({'state': 'cancelled'})

    def action_draft(self):
        cancelled_rma = self.filtered(lambda r: r.state == 'cancelled')
        cancelled_rma.write({'state': 'draft'})

    def action_lock(self):
        """Invoked when 'Lock' button in rma form view is clicked."""
        self.filtered("can_be_locked").write({"state": "locked"})

    def action_unlock(self):
        """Invoked when 'Unlock' button in rma form view is clicked."""
        locked_rma = self.filtered(lambda r: r.state == "locked")
        locked_rma.write({"state": "received"})

    def action_preview(self):
        """Invoked when 'Preview' button in rma form view is clicked."""
        self.ensure_one()
        return {
            "type": "ir.actions.act_url",
            "target": "self",
            "url": self.get_portal_url(),
        }

    def _ensure_can_be_returned(self):
        """This method is intended to be invoked after user click on
        'Replace' or 'Return to customer' button (before the delivery wizard
        is launched) and after confirm the wizard.

        This method is invoked by:
        rma.action_replace
        rma.action_return
        rma.create_replace
        rma.create_return
        """
        if len(self) == 1:
            if self.can_be_returned:  # 如果还没有退回，则需要先退回＃not self.can_be_returned
                raise ValidationError(_("This RMA cannot perform a return."))
        elif not self.filtered("can_be_returned"):
            raise ValidationError(_("None of the selected RMAs can perform a return."))

    def _ensure_can_be_replaced(self):
        """This method is intended to be invoked after user click on
        'Replace' button (before the delivery wizard
        is launched) and after confirm the wizard.

        This method is invoked by:
        rma.action_replace
        rma.create_replace
        """
        if len(self) == 1:
            if not self.can_be_replaced:
                raise ValidationError(_("This RMA cannot perform a replacement."))
        elif not self.filtered("can_be_replaced"):
            raise ValidationError(
                _("None of the selected RMAs can perform a replacement.")
            )

    def _ensure_qty_to_return(self, qty=None, uom=None):
        """This method is intended to be invoked after confirm the wizard.
        invoked by: rma.create_return
        """
        if qty and uom:
            if uom != self.product_uom:
                qty = uom._compute_quantity(qty, self.product_uom)
            if qty > self.remaining_qty:
                raise ValidationError(
                    _("The quantity to return is greater than " "remaining quantity.")
                )

    def _ensure_qty_to_extract(self, qty, uom):
        """This method is intended to be invoked after confirm the wizard.
        invoked by: rma.extract_quantity
        """
        to_split_uom_qty = qty
        if uom != self.product_uom:
            to_split_uom_qty = uom._compute_quantity(qty, self.product_uom)
        if to_split_uom_qty > self.remaining_qty:
            raise ValidationError(
                _(
                    "Quantity to extract cannot be greater than remaining"
                    " delivery quantity (%(remaining_qty)s %(product_uom)s)"
                )
                % (
                    {
                        "remaining_qty": self.remaining_qty,
                        "product_uom": self.product_uom.name,
                    }
                )
            )

    # Reception business methods
    def _create_receptions_from_picking(self):
        self.ensure_one()
        stock_return_picking_form = Form(
            self.env["stock.return.picking"].with_context(
                active_ids=self.picking_id.ids,
                active_id=self.picking_id.id,
                active_model="stock.picking",
            )
        )
        return_wizard = stock_return_picking_form.save()
        if self.location_id:
            return_wizard.location_id = self.location_id
        return_wizard.product_return_moves.filtered(
            lambda r: r.move_id != self.move_id
        ).unlink()
        return_line = return_wizard.product_return_moves
        return_line.update(
            {
                "quantity": self.product_uom_qty,
                # The to_refund field is now True by default, which isn't right in the
                # RMA creation context
                "to_refund": self.s_to_refund,  # 自动更新销售发货数
            }
        )
        # set_rma_picking_type is to override the copy() method of stock
        # picking and change the default picking type to rma picking type.
        picking_action = return_wizard.with_context(
            set_rma_picking_type=True
        ).create_returns()
        picking_id = picking_action["res_id"]
        picking = self.env["stock.picking"].browse(picking_id)
        picking.origin = "{} ({})".format(self.name, picking.origin)
        move = picking.move_ids
        move.priority = self.priority
        return move, picking

    def _create_receptions_from_product(self):
        self.ensure_one()
        picking = self.env["stock.picking"].create(self._prepare_picking_vals())
        picking.action_confirm()
        picking.action_assign()
        picking.message_post_with_view(
            "mail.message_origin_link",
            values={"self": picking, "origin": self},
            subtype_id=self.env.ref("mail.mt_note").id,
        )
        return picking.move_ids, picking

    def _prepare_picking_vals(self):
        return {
            "picking_type_id": self.warehouse_id.rma_in_type_id.id,
            "origin": self.name,
            "partner_id": self.partner_shipping_id.id,
            "location_id": self.partner_shipping_id.property_stock_customer.id,
            "location_dest_id": self.location_id.id,
            "move_ids": [(0, 0, {
                "product_id": self.product_id.id,
                # same text as origin move or product text in partner lang
                "name": self.move_id.name
                        or self.product_id.with_context(
                    lang=self.partner_id.lang or "en_US"
                ).display_name,
                "location_id": self.partner_shipping_id.property_stock_customer.id,
                "location_dest_id": self.location_id.id,
                "product_uom_qty": self.product_uom_qty,
            })],
        }

    # Extract business methods
    def extract_quantity(self, qty, uom):
        self.ensure_one()
        self._ensure_qty_to_extract(qty, uom)
        self.product_uom_qty -= uom._compute_quantity(qty, self.product_uom)
        if self.remaining_qty_to_done <= 0:
            if self.state == "waiting_return":
                self.state = "returned"
            elif self.state == "waiting_replacement":
                self.state = "replaced"
        extracted_rma = self.copy(
            {
                "origin": self.name,
                "product_uom_qty": qty,
                "product_uom": uom.id,
                "state": "received",
                "reception_move_id": self.reception_move_id.id,
                "origin_split_rma_id": self.id,
            }
        )
        extracted_rma.message_post_with_view(
            "mail.message_origin_link",
            values={"self": extracted_rma, "origin": self},
            subtype_id=self.env.ref("mail.mt_note").id,
        )
        self.message_post(
            body=_(
                'Split: <a href="#" data-oe-model="rma" '
                'data-oe-id="%(id)d">%(name)s</a> has been created.'
            )
                 % ({"id": extracted_rma.id, "name": extracted_rma.name})
        )
        return extracted_rma

    # Refund business methods
    def _prepare_refund_vals(self, origin=False):
        """Hook method for preparing the refund Form.

        This method could be override in order to add new custom field
        values in the refund creation.

        invoked by:
        rma.action_refund
        """
        self.ensure_one()
        return {
            "move_type": "out_refund",
            "company_id": self.company_id.id,
            "partner_id": self.partner_invoice_id.id,
            "invoice_payment_term_id": False,
            "invoice_origin": origin,
            "invoice_line_ids": [],
        }

    def _prepare_refund_line_vals(self):
        """Hook method for preparing a refund line Form.

        This method could be override in order to add new custom field
        values in the refund line creation.

        invoked by:
        rma.action_refund
        """
        self.ensure_one()
        return {
            "product_id": self.product_id.id,
            "quantity": self.product_uom_qty,
            "product_uom_id": self.product_uom.id,
            "price_unit": self.product_id.lst_price,
            "s_rma_id": self.id,
        }

    # Returning business methods
    def create_return(self, scheduled_date, qty=None, uom=None):
        """Intended to be invoked by the delivery wizard"""
        group_returns = self.env.company.rma_return_grouping
        if "rma_return_grouping" in self.env.context:
            group_returns = self.env.context.get("rma_return_grouping")
        self._ensure_can_be_returned()
        self._ensure_qty_to_return(qty, uom)
        group_dict = {}
        rmas_to_return = self.filtered("can_be_returned")
        for record in rmas_to_return:
            key = (
                record.partner_shipping_id.id,
                record.company_id.id,
                record.warehouse_id,
            )
            group_dict.setdefault(key, self.env["rma"])
            group_dict[key] |= record
        if group_returns:
            grouped_rmas = group_dict.values()
        else:
            grouped_rmas = rmas_to_return
        for rmas in grouped_rmas:
            origin = ", ".join(rmas.mapped("name"))
            picking_vals = rmas[0]._prepare_returning_picking_vals(origin)
            for rma in rmas:
                picking_vals["move_ids"].append(
                    (0, 0, rma._prepare_returning_move_vals(scheduled_date, qty, uom))
                )
            picking = self.env["stock.picking"].create(picking_vals)
            for rma in rmas:
                rma.message_post(
                    body=_(
                        'Return: <a href="#" data-oe-model="stock.picking" '
                        'data-oe-id="%(id)d">%(name)s</a> has been created.'
                    )
                         % ({"id": picking.id, "name": picking.name})
                )
            picking.action_confirm()
            picking.action_assign()
            picking.message_post_with_view(
                "mail.message_origin_link",
                values={"self": picking, "origin": rmas},
                subtype_id=self.env.ref("mail.mt_note").id,
            )
        rmas_to_return.write({"state": "waiting_return"})

    def _prepare_returning_picking_vals(self, origin=None):
        self.ensure_one()
        return {
            "picking_type_id": self.warehouse_id.rma_out_type_id.id,
            "location_id": self.location_id.id,
            "location_dest_id": self.reception_move_id.location_id.id,
            "origin": origin or self.name,
            "partner_id": self.partner_shipping_id.id,
            "company_id": self.company_id.id,
            "move_ids": [],
        }

    def _prepare_returning_move_vals(self, scheduled_date, quantity=None, uom=None):
        self.ensure_one()
        return {
            "product_id": self.product_id.id,
            "name": self.product_id.with_context(
                lang=self.partner_shipping_id.lang or "en_US"
            ).display_name,
            "product_uom_qty": quantity or self.product_uom_qty,
            "product_uom": uom and uom.id or self.product_uom.id,
            "location_id": self.location_id.id,
            "location_dest_id": self.reception_move_id.location_id.id,
            "date": scheduled_date,
            "s_rma_id": self.id,
            "move_orig_ids": [(4, self.reception_move_id.id)],
            "company_id": self.company_id.id,
        }

    def _prepare_procurement_values(self, group_id, scheduled_date, warehouse):
        self.ensure_one()
        return {
            "company_id": self.company_id,
            "group_id": group_id,
            "date_planned": scheduled_date,
            "warehouse_id": warehouse,
            "partner_id": self.partner_shipping_id.id,
            "rma_id": self.id,
            "priority": self.priority,
        }

    # Mail business methods
    def _creation_subtype(self):
        if self.state in ("draft"):
            return self.env.ref("rma.mt_rma_draft")
        else:
            return super()._creation_subtype()

    def _track_subtype(self, init_values):
        self.ensure_one()
        if "state" in init_values:
            if self.state == "draft":
                return self.env.ref("rma.mt_rma_draft")
            elif self.state == "confirmed":
                return self.env.ref("rma.mt_rma_notification")
        return super()._track_subtype(init_values)

    def message_new(self, msg_dict, custom_values=None):
        """Extract the needed values from an incoming rma emails data-set
        to be used to create an RMA.
        """
        if custom_values is None:
            custom_values = {}
        subject = msg_dict.get("subject", "")
        body = html2plaintext(msg_dict.get("body", ""))
        desc = _(
            "<b>E-mail subject:</b> %(subject)s<br/><br/><b>E-mail"
            " body:</b><br/>%(body)s"
        ) % ({"subject": subject, "body": body})
        defaults = {
            "description": desc,
            "name": _("New"),
            "origin": _("Incoming e-mail"),
        }
        if msg_dict.get("author_id"):
            partner = self.env["res.partner"].browse(msg_dict.get("author_id"))
            defaults.update(
                partner_id=partner.id,
                partner_invoice_id=partner.address_get(["invoice"]).get(
                    "invoice", False
                ),
            )
        if msg_dict.get("priority"):
            defaults["priority"] = msg_dict.get("priority")
        defaults.update(custom_values)
        rma = super().message_new(msg_dict, custom_values=defaults)
        if rma.user_id and rma.user_id.partner_id not in rma.message_partner_ids:
            rma.message_subscribe([rma.user_id.partner_id.id])
        return rma
    #
    # @api.returns("mail.message", lambda value: value.id)
    # def message_post(self, **kwargs):
    #     """Set 'sent' field to True when an email is sent from rma form
    #     view. This field (sent) is used to set the appropriate style to the
    #     'Send by Email' button in the rma form view.
    #     """
    #     if self.env.context.get("mark_rma_as_sent"):
    #         self.write({"sent": True})
    #     # mail_post_autofollow=True to include email recipient contacts as
    #     # RMA followers
    #     self_with_context = self.with_context(mail_post_autofollow=True)
    #     return super(Rma, self_with_context).message_post(**kwargs)

    def _message_get_suggested_recipients(self):
        recipients = super()._message_get_suggested_recipients()
        try:
            for record in self.filtered("partner_id"):
                record._message_add_suggested_recipient(
                    recipients, partner=record.partner_id, reason=_("Customer")
                )
        except AccessError as e:  # no read access rights
            _logger.debug(e)
        return recipients

    # Reporting business methods
    def _get_report_base_filename(self):
        self.ensure_one()
        return "RMA Report - %s" % self.name

    # Other business methods

    def update_received_state_on_reception(self):
        """Invoked by:
        [stock.move]._action_done
        Here we can attach methods to trigger when the customer products
        are received on the RMA location, such as automatic notifications
        """
        self.write({"state": "received"})
        self._send_receipt_confirmation_email()

    def update_received_state(self):
        """Invoked by:
        [stock.move].unlink
        [stock.move]._action_cancel
        """
        rma = self.filtered(lambda r: r.delivered_qty == 0)
        if rma:
            rma.write({"state": "received"})

    def update_replaced_state(self):
        """Invoked by:
        [stock.move]._action_done
        [stock.move].unlink
        [stock.move]._action_cancel
        """
        rma = self.filtered(
            lambda r: (
                    r.state == "waiting_replacement"
                    and 0 >= r.remaining_qty_to_done == r.remaining_qty
            )
        )
        if rma:
            rma.write({"state": "replaced"})

    def update_returned_state(self):
        """Invoked by [stock.move]._action_done"""
        rma = self.filtered(
            lambda r: (r.state == "waiting_return" and r.remaining_qty_to_done <= 0)
        )
        if rma:
            rma.write({"state": "returned"})

    def create_rework(self, qty, rework_bom_id):
        self.ensure_one()
        # 调拨至生产前
        self._picking_product(qty)
        # 创建成品检修单
        productions = self._create_rework_production(qty, rework_bom_id)
        productions.action_confirm()

    def _picking_product(self, qty):
        self.ensure_one()
        # location_id = self.env['stock.location'].search([('barcode', '=', 'WH-STOCK-FINISHED-PRODUCT')], limit=1)
        location_id = self.env['stock.location'].search([('barcode', '=', 'WH-RMA')], limit=1)
        # location_dest_id = self.env['stock.location'].search([('barcode', '=', 'WH-PREPRODUCTION')], limit=1)
        picking_type = self.env['stock.picking.type'].search([('barcode', '=', 'WTT-INTERNAL-REWORK')], limit=1)

        picking_vals = {
            'origin': self.name,
            'company_id': self.env.user.company_id.id,
            'location_id': location_id.id,
            'location_dest_id': picking_type.default_location_dest_id.id,
            'picking_type_id': picking_type.id,
        }
        picking = self.env['stock.picking'].sudo().create(picking_vals)
        move_ids = self.s_rma_move_ids.filtered(lambda x: x.state == 'done').sorted(lambda x: x.product_id.id)

        move_vals_list = []
        for product_id, g in groupby(move_ids, key=lambda x: x.product_id):
            g_move_ids = self.env['stock.move'].concat(*g)
            vals = {
                'picking_id': picking.id,
                'name': product_id.name,
                'product_id': product_id.id,
                'location_id': location_id.id,
                'location_dest_id': picking_type.default_location_dest_id.id,
                'product_uom_qty': sum(g_move_ids.mapped('quantity')),
            }
            move_line_vals_list = []
            for line in g_move_ids.mapped('move_line_ids'):
                move_line_vals_list.append((0, 0, {
                    'product_id': product_id.id,
                    'product_uom_id': self.product_uom.id,
                    'location_id': location_id.id,
                    'location_dest_id': picking_type.default_location_dest_id.id,
                    'picking_id': picking.id,
                    'company_id': self.env.user.company_id.id,
                    'quantity': line.quantity,
                    'lot_id': line.lot_id.id
                }))
            vals['move_line_ids'] = move_line_vals_list
            move_vals_list.append(vals)

        self.env['stock.move'].create(move_vals_list)
        picking.action_confirm()
        return picking

    def _create_remake_production(self, product_id, planned_quantity):
        """
        生成生产单
        @param product_id: 产品
        @param planned_panel_quantity: 计划投料张数
        @param planned_quantity: 计划投料的数量
        @param uom_panel: 单位 pnl 数量
        """
        self.ensure_one()
        picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1)
        base_mo = None
        quantity = planned_quantity
        vals = {
            'consumption': 'strict',
            'date_start': datetime.datetime.now(),
            'mo_type': 'M',
            'product_qty': quantity,
            'picking_type_id': picking_type.id,
            'location_dest_id': picking_type.default_location_dest_id.id,
            'location_src_id': picking_type.default_location_src_id.id,
            'base_mo': base_mo,
            'company_id': self.s_order_ids[0].company_id.id,
            'origin': '',
            'product_id': product_id.id,
            'product_uom_id': product_id.uom_id.id,
            'rma_id': self.id,
            's_order_type': 'N'
        }
        production_ids = self.env['mrp.production'].with_user(self.env.user).sudo().with_company(self.env.company.id).create(vals)
        # production_ids = self.env['mrp.production'].create([vals])
        return production_ids

    def _create_rework_production(self, qty, rework_bom_id):
        """
        创建成品检修制造单
        """
        self.ensure_one()
        picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1)
        location_id = self.env['stock.location'].search([('barcode', '=', 'WH-REPAIR')], limit=1)
        vals = {
            'mo_type': 'W',
            'product_id': self.product_id.id,
            'bom_id': False,
            'picking_type_id': picking_type.id,
            'location_src_id': location_id.id,
            'product_qty': qty,
            'product_uom_id': self.product_id.uom_id.id,
            'user_id': self.env.user.id,
            'origin': self.name,
            'srce_rm_mo': self.name,
            's_rma_id': self.id,
            's_order_type': 'R',
        }
        body = ""
        productions = self.env['mrp.production'].with_user(self.env.user).sudo().with_company(self.env.company.id).create(vals)

        # sale_line_id = self.move_id.sale_line_id
        s_mrp_production_ids = []
        for prod in productions:
            body += _("Rework-") + " : " + prod.name + "\n"

            # 根据检修bom，更新检修工序
            # 检修，不需要物料消耗
            prod.move_raw_ids.unlink()
            prod.mapped('workorder_ids').unlink()
            if not rework_bom_id.line_ids.filtered(lambda x: x.workcenter_id.code == 'PKG01'):
                # 检修没有包装流程时自动加上包装工序
                pack_workcenter_id = self.env['mrp.workcenter'].search([('code', '=', 'PKG01')], limit=1)
                step = len(rework_bom_id.line_ids) + 1
                s = '%02d' % step
                name = s + '.' + pack_workcenter_id.display_name.strip()
                work_station_id = pack_workcenter_id.work_station_id.id
                self.env['s.remork.bom.line'].create([{
                    'step': len(rework_bom_id.line_ids) + 1,
                    'name': name,
                    'work_station_id': work_station_id,
                    'workcenter_id': pack_workcenter_id.id,
                    'rework_bom_id': rework_bom_id.id
                }])
            self.env['mrp.workorder'].create([{
                'name': bom_line.name,
                'workcenter_id': bom_line.workcenter_id.id,
                'production_id': prod.id,
                'product_uom_id': self.product_uom.id,
                'work_station_id': bom_line.work_station_id.id,
                'step': bom_line.step
            } for bom_line in rework_bom_id.line_ids
            ])
            # s_mrp_production_ids.append((4, prod.id))

        productions.write({'move_raw_ids': [(0, 0, {
            'company_id': self.env.user.company_id.id,
            'date': datetime.datetime.now(),
            'location_dest_id': picking_type.default_location_dest_id.id,
            'location_id': location_id.id,
            'name': self.product_id.display_name,
            'procure_method': 'make_to_stock',
            'product_id': self.product_id.id,
            'product_uom': self.product_id.uom_id.id,
            'product_uom_qty': qty,
            'unit_factor': 1,
        })]})
        # sale_line_id.write({'s_mrp_production_ids': s_mrp_production_ids})
        return productions

    def create_picking(self, production_ids):
        """
            创建板材的合并领料单
        """
        if not production_ids:
            return

        picking_type = self.env['stock.picking.type'].search([('code', '=', 'internal'), ('barcode', '=', 'WTT-CB')], limit=1)
        move_vals_list = []
        all_production = production_ids
        for p in production_ids:
            all_production |= p._get_all_child()
        for production in all_production:
            for line in production.move_raw_ids:
                product = line.product_id
                if product.categ_id.parent_id and product.categ_id.parent_id.code and product.categ_id.parent_id.code == 'CR':
                    vals = {
                        'company_id': self.env.user.company_id.id,
                        'date': datetime.datetime.now(),
                        'location_id': picking_type.default_location_src_id.id,
                        'location_dest_id': picking_type.default_location_dest_id.id,
                        'product_id': line.product_id.id,
                        'name': line.product_id.display_name,
                        'procure_method': 'make_to_stock',
                        'product_uom': line.product_id.uom_id.id,
                        'product_before_rounding_qty': line.panel_need_qty,
                        'product_uom_qty': math.ceil(line.panel_need_qty),
                    }
                    move_vals_list.append((0, 0, vals))

        if move_vals_list:
            vals = {
                'company_id': self.env.user.company_id.id,
                'location_id': picking_type.default_location_src_id.id,
                'location_dest_id': picking_type.default_location_dest_id.id,
                'move_type': 'direct',
                'picking_type_id': picking_type.id,
                's_material_order': True,
                'move_ids': move_vals_list,
                'origin': ','.join(list(set(production_ids.mapped('base_mo')))) + ' / ' + self.product_id.name
            }
            material_picking_id = self.env['stock.picking'].create(vals)
            return material_picking_id

    def action_remake(self):
        """根据报废数重制"""
        self.ensure_one()
        if not self.refund_line_ids:
            raise UserError(_('Please refund first!'))

        action = self.env["ir.actions.act_window"]._for_xml_id("pcbmanufact.s_supp_plan_material_requirement_wizard_action")
        uom_value_ids = self.product_id.uom_value_ids
        uom_values = {x.uom_id.code: x.uom_value for x in uom_value_ids}
        value_pnl = uom_values.get('PANEL', 1)
        qty_pnl = math.ceil(self.scrap_qty / value_pnl)
        base_mo_id = self.env['s.mrp.base']
        if self.rm_production_ids:
            base_mo_id = self.rm_production_ids[0].s_base_mo_id
        else:
            return_line_ids = self.s_return_line_ids.filtered(lambda x: x.return_qty > 0)
            if return_line_ids:
                base_mo_id = return_line_ids[0].sale_line_id.s_mrp_production_ids[0].s_base_mo_id
        action["context"] = {
            'base_id': base_mo_id.id,
            'supplement_pnl_qty': qty_pnl
        }
        return action

    ################################ 跳转 ##########################################

    def action_view_rm(self):
        tree_id = self.env.ref('mrp.mrp_production_tree_view').id
        form_id = self.env.ref('mrp.mrp_production_form_view').id
        return {
            'name': 'Rework/Reprocessing',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form, tree',
            'res_model': 'mrp.production',
            'domain': [('id', 'in', self.rm_production_ids.ids)],
            'views': [(tree_id, 'tree'), (form_id, 'form')],
            'context': {'create': 0},
        }

    def action_rework(self):
        """Invoked when 'Rework' button in rma form view is clicked."""
        self.ensure_one()
        if not self.refund_line_ids:
            raise UserError('Please refund first!')

        action = (
            self.env.ref("rma.rma_rework_wizard_action")
            .sudo()
            .with_context(active_id=self.id)
            .read()[0]
        )
        action["name"] = _("Rework/Reprocessing")
        action["context"] = dict(self.env.context)
        rework_bom_id = self.env['s.rework.bom'].search([('product_id', '=', self.product_id.id)], limit=1)
        action["context"].update(
            active_id=self.id,
            active_ids=self.ids,
            rma_action_type="rework",
            default_rework_flag=True,
            default_rework_bom_id=rework_bom_id.id if rework_bom_id else None,
        )
        return action

    def action_return(self):
        """Invoked when 'Return to customer' button in rma form
        view is clicked.
        """
        self.ensure_one()
        self._ensure_can_be_returned()
        # Force active_id to avoid issues when coming from smart buttons
        # in other models
        action = (
            self.env.ref('rma.rma_delivery_wizard_action')
            .sudo()
            .with_context(active_id=self.id)
            .read()[0]
        )
        action['context'] = dict(self.env.context)
        action['context'].update(
            active_id=self.id,
            active_ids=self.ids,
            rma_delivery_type='return',
        )
        return action

    def action_view_receipt(self):
        """跳转退货."""
        self.ensure_one()
        action = self.env['ir.actions.act_window']._for_xml_id('stock.action_picking_tree_all')
        action['domain'] = [('s_rma_id', '=', self.id)]
        return action

    def action_view_refund(self):
        """Invoked when 'Refund' smart button in rma form view is clicked."""
        self.ensure_one()
        account_move_ids = self.refund_line_ids.mapped('move_id')
        if len(account_move_ids) > 1:
            return {
                'name': _('Refund'),
                'type': 'ir.actions.act_window',
                'res_model': 'account.move',
                'view_mode': 'form',
                'domain': [('id', 'in', account_move_ids.ids)],
            }
        return {
            'name': _('Refund'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
            'res_id': account_move_ids.id,
            'view_mode': 'form',
        }

    def action_view_delivery(self):
        """Invoked when 'Delivery' smart button in rma form view is clicked."""
        action = (
            self.env.ref("stock.action_picking_tree_all")
            .sudo()
            .with_context(active_id=self.id)
            .read()[0]
        )
        picking = self.delivery_move_ids.mapped("picking_id")
        if len(picking) > 1:
            action["domain"] = [("id", "in", picking.ids)]
        elif picking:
            action.update(
                res_id=picking.id,
                view_mode="form",
                view_id=False,
                views=False,
            )
        return action